ОБЯЗАТЕЛЬНОДЛЯ НОВИЧКОВНЕ ДЛЯ НОВИЧКОВВ РАЗРАБОТКЕ
Разработчику
Архитектору
Инженеру
Справочник по iOS
1.1. Info.plist — основные ключи и допустимые значения
Все ключи в Info.plist являются строками, но значения — строго типизированы: <string>, <integer>, <array>, <dict>, <bool>, <data>.
| Ключ (ключ в plist / CFBundle) | Тип значения | Описание | Возможные значения / Формат | Примечания |
|---|
CFBundleDisplayName | string | Отображаемое имя приложения (локализуемо через InfoPlist.strings) | MyApp | Приоритет выше, чем у CFBundleName. |
CFBundleName | string | Краткое имя (≤15 символов для Springboard) | MyApp | Используется при невозможности отобразить DisplayName. |
CFBundleIdentifier | string | Уникальный bundle ID в reverse-DNS формате | com.example.MyApp | Неизменяем после публикации в App Store. |
CFBundleVersion | string | Build number (технический номер сборки) | 1, 1.2.3, 20251121.1 | Должен возрастать монотонно при каждой сборке. |
CFBundleShortVersionString | string | Marketing version (версия для пользователя) | 1.0, 2.1.5 | Соответствует version в App Store Connect. |
LSRequiresIPhoneOS | boolean | Приложение требует iOS (а не macOS/iPadOS в legacy-режиме) | true | Обязательно для всех современных iOS-приложений. |
UIApplicationSceneManifest | dict | Манифест сцен (для многозадачности, iPad, macOS Catalyst) | словарь с UISceneConfigurations | Используется вместо UISupportedInterfaceOrientations при наличии нескольких сцен. |
UISupportedInterfaceOrientations | array<string> | Поддерживаемые ориентации (устаревшее для сцен) | UIInterfaceOrientationPortrait, UIInterfaceOrientationLandscapeLeft, UIInterfaceOrientationPortraitUpsideDown, UIInterfaceOrientationLandscapeRight | Применяется только если не используется SceneDelegate. |
UISupportedInterfaceOrientations~ipad | array<string> | То же, но для iPad | как выше | |
UIRequiredDeviceCapabilities | array<string> | Жёсткие требования к железу | arm64, metal, gps, telephony, wifi, accelerometer, gyro, nfc, camera-front, camera-rear, gamekit, location-services, microphone, opengles-3, opengles-2, bluetooth-le, arkit | Приложение не установится, если устройство не поддерживает хотя бы один из указанных. Можно использовать !-префикс для исключения (редко, например !telephony для iPad-only). |
UIBackgroundModes | array<string> | Режимы фоновой работы (требуют entitlement'ов и justification в App Review) | audio, location, voip, fetch, remote-notification, bluetooth-central, bluetooth-peripheral, background-processing, continuous-waveform-processing, app-events | Требуется описание использования в App Store Connect. |
NSLocationWhenInUseUsageDescription | string | Запрос доступа к геолокации «во время использования» | текст на локализованном языке | Обязателен при вызове requestWhenInUseAuthorization(). |
NSLocationAlwaysAndWhenInUseUsageDescription | string | Для requestAlwaysAuthorization() | текст | Без этого ключа запрос always игнорируется. |
NSCameraUsageDescription | string | Обоснование использования камеры | текст | |
NSPhotoLibraryUsageDescription | string | Доступ к медиабиблиотеке | текст | |
NSMicrophoneUsageDescription | string | Доступ к микрофону | текст | |
NSBluetoothAlwaysUsageDescription | string | Для CoreBluetooth в фоне (начиная с iOS 13) | текст | |
NSBluetoothPeripheralUsageDescription | string | Для iOS ≤12 / совместимости | текст | |
NSAppTransportSecurity | dict | ATS (App Transport Security) настройки | NSAllowsArbitraryLoads: <bool>, NSExceptionDomains: <dict<domain, dict>> | Рекомендуется не отключать NSAllowsArbitraryLoads, а использовать исключения. |
ITSAppUsesNonExemptEncryption | boolean | Использует ли приложение криптографию, подпадающую под экспортные ограничения США | true/false | Если false — не требуется подача EAR99 declaration. |
UIFileSharingEnabled | boolean | Включает файловый шеринг через iTunes/Files.app | true | Даёт доступ к Documents/ через Files.app. |
LSSupportsOpeningDocumentsInPlace | boolean | Поддержка прямого редактирования документов (для UIDocument) | true | Требуется вместе с UIFileSharingEnabled для iCloud-документов. |
UIApplicationSupportsIndirectInputEvents | boolean | Поддержка внешних клавиатур/контроллеров (iPadOS/macOS Catalyst) | true | Иначе события могут обрабатываться некорректно. |
UIUserInterfaceStyle | string | Принудительная тема интерфейса | Light, Dark, Automatic | Automatic — значение по умолчанию. |
NSHumanReadableCopyright | string | Авторские права (отображается в App Store) | © 2025 Timur Tagirov | |
CFBundleDevelopmentRegion | string | Язык разработки по умолчанию (для локализации) | en, ru, Base | Используется, если локализация не найдена. |
CFBundleLocalizations | array<string> | Список поддерживаемых языков | ["en", "ru", "es"] | Влияет на порядок выбора локали. |
CFBundleURLTypes | array<dict> | Поддержка кастомных URL-схем | [ { "CFBundleURLName": "com.example.myapp", "CFBundleURLSchemes": ["myapp"] } ] | Регистрация myapp:// scheme. |
LSApplicationQueriesSchemes | array<string> | Схемы, которые приложение может проверять через canOpenURL: | ["whatsapp", "tg", "vk", "fb"] | Без этого canOpenURL: вернёт false даже если приложение установлено. |
1.2. Launch Options (application(_:didFinishLaunchingWithOptions:))
При запуске приложения система передаёт словарь launchOptions — ключи из UIApplication.LaunchOptionsKey.
Ключ (Swift: UIApplication.LaunchOptionsKey) | Тип значения | Описание | Когда появляется |
|---|
.annotation | Any | Пользовательские данные, переданные через UIApplication.open(_:options:completionHandler:) | При открытии через custom URL или Universal Link с аннотацией |
.bluetoothCentralsKey | [String] | UUID'ы центральных менеджеров CoreBluetooth | При восстановлении после терминирования в фоне |
.bluetoothPeripheralsKey | [String] | UUID'ы периферийных устройств | То же |
.cloudKitShareMetadataKey | CKShare.Metadata | Метаданные приглашения CloudKit | При открытии shared-контента |
.locationKey | CLLocation | Последняя известная локация | При запуске из-за significant location change или region monitoring |
.localNotificationKey | UILocalNotification (iOS ≤9) | Устарело | — |
.remoteNotificationKey | [AnyHashable : Any] | Payload APNs | При запуске через push-уведомление с "content-available": 1 или interactive push |
.sourceApplication | String (bundle ID) | Bundle ID приложения-инициатора | При открытии через URL scheme или Universal Link из другого приложения |
.url | URL | URL, по которому открыто приложение | myapp://action?id=123, https://example.com/deep-link |
.userActivityDictionaryKey | [String: NSUserActivity] | Activity для Handoff / Spotlight | При продолжении activity |
.userActivityType | String | Тип activity (устаревший способ) | — |
⚠️ Начиная с iOS 13, если используется SceneDelegate — launch options приходят в scene(_:willConnectTo:options:) через UIScene.ConnectionOptions.
1.3. Application State Properties (UIApplication.shared.applicationState)
Значение (UIApplication.State) | Описание | Типичные действия |
|---|
.active | Приложение на переднем плане и получает события | Полный UI-ввод, анимации, обновление данных в реальном времени |
.inactive | Приложение на переднем плане, но не получает события | Вызов Siri, Control Center, incoming call, переход в multitasking, lock screen. Следует паузить таймеры/аудио/камеру. |
.background | Приложение в фоне и может выполнять ограниченные задачи | Фоновые fetch, location updates, Bluetooth, completion handlers. По истечении ~30 сек (beginBackgroundTask) — suspend. |
Изменения состояния улавливаются через делегаты:
applicationWillResignActive(_:)
applicationDidEnterBackground(_:)
applicationWillEnterForeground(_:)
applicationDidBecomeActive(_:)
1.4. Основные runtime environment attributes (доступны через ProcessInfo, UIDevice, UIScreen)
| Источник | Свойство | Тип | Пример значения | Примечание |
|---|
ProcessInfo.processInfo | .environment["XPC_SERVICE_NAME"] | String? | "UIKitApplication:com.example.MyApp[0xdeadbeef]" | Идентификатор процесса в XPC |
| .operatingSystemVersion | OperatingSystemVersion | majorVersion: 18, minorVersion: 1, patchVersion: 0 | Не используйте UIDevice.systemVersion — он устарел и возвращает строку, подверженную spoofing'у. |
| .hostname | String? | "Timurs-iPhone" | |
| .globallyUniqueString | String | уникальный UUID-like стринг | Для временных имён файлов/директорий |
UIDevice.current | .name | String | "iPhoneTimur" | Настраивается пользователем — ненадёжно |
| .model | String | "iPhone", "iPad" | Не даёт конкретной модели (см. ниже) |
| .localizedModel | String | "iPhone", "iPod touch" | |
| .systemName | String | "iOS" | Или "iPadOS" начиная с 13 |
| .identifierForVendor | UUID? | A8D5D2E1-... | Сбрасывается при удалении всех приложений vendor’а. |
| .isBatteryMonitoringEnabled + .batteryLevel, .batteryState | Float, UIDevice.BatteryState | 0.75, .charging | Требует включения мониторинга |
UIScreen.main | .bounds, .nativeBounds | CGRect | {x:0 y:0 w:390 h:844} (iPhone 14 Pro) | nativeBounds — в physical pixels |
| .scale | CGFloat | 3.0 | Для Retina HD (iPhone 14 Pro) |
| .nativeScale | CGFloat | 3.0 | То же, но всегда physical/native |
| .preferredMode / .availableModes | UIScreen.Mode | ширина/высота в pixels, refresh rate | Например: { width: 1170, height: 2532, pixelFormat: 875704916, refreshRate: 120.0 } |
NSProcessInfo | .processName | String | "MyApp" | Имя executable по умолчанию — CFBundleExecutable. |
🔍 Для определения конкретной модели устройства (iPhone16,3 и т.п.) — читайте hw.machine через sysctlbyname("hw.machine", ...). Примеры:
📘 UI/UIKit Core
Фокус — на публичных, часто используемых, диагностически важных свойствах, включая runtime-only атрибуты (например, доступные только в debug-режиме через private API, если они стабильны и полезны для анализа).
Все свойства приведены в формате Swift, с типами и семантическими пояснениями.
Где уместно — указаны аналоги в SwiftUI (через UIViewRepresentable/UIViewControllerRepresentable или environment keys).
2.1. UIView — основные свойства
| Свойство | Тип | Описание | Значения / Особенности |
|---|
frame | CGRect | Позиция и размер в координатах superview | Изменение frame пересчитывает bounds.origin и center. Не используйте при наличии transform ≠ .identity. |
bounds | CGRect | Локальная система координат: origin — точка (0,0) вьюхи, size — её размер | bounds.origin влияет на contentOffset потомков (например, для скролла вручную). |
center | CGPoint | Центр вьюхи в координатах superview | Предпочтительный способ позиционирования при анимации центра. |
transform | CGAffineTransform | 2D-трансформация (scale, rotate, translate) | Не влияет на frame (становится «undefined»). При transform ≠ .identity — frame игнорируется layout engine’ом. |
layer.transform | CATransform3D | 3D-трансформация (layer-only) | Не затрагивает layout (bounds/frame сохраняют «логическое» положение). |
alpha | CGFloat | Прозрачность (0.0–1.0) | Значения < 0.01 приводят к isHidden = true на уровне GPU (оптимизация). |
isHidden | Bool | Видимость (не влияет на layout) | Скрытые вьюхи не получают hitTest, не анимируются. |
isUserInteractionEnabled | Bool | Обработка касаний (true по умолчанию) | Значение false блокирует всеgesture recognizers поддерева. |
clipsToBounds | Bool | Обрезка содержимого за пределы bounds | Эквивалент layer.masksToBounds. |
autoresizingMask | UIView.AutoresizingMask | Устаревший механизм autosizing (pre-Auto Layout) | .flexibleWidth, .flexibleHeight, .flexibleTopMargin и др. Не смешивать с constraints. |
translatesAutoresizingMaskIntoConstraints | Bool | Создавать ли constraints из autoresizingMask | Обязательно false при использовании программных constraints. По умолчанию — true. |
semanticContentAttribute | UISemanticContentAttribute | Направление контента (для LTR/RTL) | .unspecified, .playback, .spatial, .forceLeftToRight, .forceRightToLeft |
layoutMargins | NSDirectionalEdgeInsets | Отступы от краёв для layout (с учётом safe area) | Чтение: системные (например, от notch). Запись: если preservesSuperviewLayoutMargins = false. |
directionalLayoutMargins | NSDirectionalEdgeInsets | То же, но всегда directional (top, leading, bottom, trailing) | Предпочтительнее layoutMargins. |
insetsLayoutMarginsFromSafeArea | Bool | Включать ли safe area в layoutMargins | По умолчанию true. |
tintColor | UIColor | Цвет интерактивных элементов (buttons, bars) | Наследуется от window.tintColor → rootViewController.view.tintColor → иерархия вьюх. |
tintAdjustmentMode | UIView.TintAdjustmentMode | Поведение tint при inactive state | .automatic (меняется на grayscale в background/inactive), .normal, .dimmed |
contentMode | UIView.ContentMode | Поведение draw(_:) и sublayers при изменении bounds | .scaleToFill, .scaleAspectFit, .scaleAspectFill, .redraw, .center, .top, .bottomRight и др. |
backgroundColor | UIColor? | Фон (рендерится через layer) | nil — прозрачный. Не влияет на isOpaque — его нужно выставлять отдельно. |
isOpaque | Bool | Подсказка рендереру: вьюха не содержит прозрачных пикселей | Ускоряет композитинг. По умолчанию true, если backgroundColor непрозрачный. |
contentScaleFactor | CGFloat | Масштаб для draw(_:) (pixels per point) | По умолчанию = UIScreen.main.scale. Можно увеличить для high-res custom drawing. |
gestureRecognizers | [UIGestureRecognizer]? | Все прикреплённые recognizers | Только чтение. Добавлять через addGestureRecognizer(_:). |
superview, subviews, window | UIView?, [UIView], UIWindow? | Иерархия | window ≠ nil означает, что вьюха визуализируется на экране. |
canBecomeFocused | Bool | Поддержка focus engine (TV/iPadOS with pointer) | true по умолчанию для UIButton, UITextField, UIControl. Переопределяется в кастомных вьюхах. |
focusEffect | UIFocusEffect? | Эффект фокуса (например, UIFocusHaloEffect()) | Можно кастомизировать или отключить (nil). |
🔧 Debug-only (через po view.value(forKey: "_...") в lldb):
_layerContentsFormat: "BGRA8888", "RGBA16F" — формат буфера rendering
_viewDelegate: UIViewController? — контроллер, отвечающий за вьюху
_gestureInfo: внутренний объект recognizers
_layerTreeAsASCIIString: текстовое дерево layers (удобно для анализа overdrawing)
2.2. UIViewController
| Свойство | Тип | Описание |
|---|
view | UIView | Основной view controller’а. Ленивая инициализация через loadView()/viewDidLoad(). |
viewIfLoaded | UIView? | view, если уже загружен, иначе nil. Безопасный способ проверить, не вызывая загрузку. |
isViewLoaded | Bool | Был ли вызван loadView() (даже если viewDidLoad() ещё не сработал). |
parent | UIViewController? | Родительский VC (например, в UINavigationController, UITabBarController, container VC). |
children | [UIViewController] | Дочерние VC (через containment API: addChild(), removeFromParent()). |
navigationController | UINavigationController? | Если VC находится в стеке навигации. |
tabBarController | UITabBarController? | Аналогично. |
splitViewController | UISplitViewController? | Для iPad/multitasking. |
traitCollection | UITraitCollection | Актуальные traits (см. ниже). |
overrideTraitCollection | UITraitCollection? | Принудительная подмена traits для child VC (например, .init(userInterfaceStyle: .dark)). |
modalPresentationStyle | UIModalPresentationStyle | Стиль модального показа: .fullScreen, .pageSheet, .formSheet, .popover, .overFullScreen, .automatic. |
modalTransitionStyle | UIModalTransitionStyle | Анимация входа: .coverVertical, .flipHorizontal, .crossDissolve, .partialCurl. |
isBeingPresented, isBeingDismissed, isMovingToParent, isMovingFromParent | Bool | Флаги во время переходов. Полезны в viewWillAppear(_:) для определения контекста. |
hidesBottomBarWhenPushed | Bool | Скрывать tab bar при пуш в навигации. |
definesPresentationContext | Bool | Определяет, будет ли этот VC «владельцем» presentation context’а (например, для search bar в navigation bar). |
providesPresentationContextTransitionStyle | Bool | Использовать modalTransitionStyle этого VC при модальном показе из него. |
navigationItem | UINavigationItem | Контент navigation bar (title, buttons, search bar). |
toolbarItems | [UIBarButtonItem]? | Элементы toolbar’а (если isToolbarHidden = false). |
useToolbar | Bool | Устаревшее (iOS ≤4). Сейчас — navigationController?.setToolbarHidden(_:animated:). |
⚠️ Lifecycle hooks (в порядке вызова):
loadView() → viewDidLoad() → viewWillAppear(_:) → viewWillLayoutSubviews() → viewDidLayoutSubviews() → viewDidAppear(_:)
При уходе: viewWillDisappear(_:) → viewDidDisappear(_:)
При low-memory: didReceiveMemoryWarning()
При rotation: viewWillTransition(to:with:), willTransition(to:with:)
2.3. UIWindow
| Свойство | Тип | Описание |
|---|
rootViewController | UIViewController? | Корневой VC. Замена требует анимации или makeKeyAndVisible(). |
windowScene | UIWindowScene? | Сцена, к которой прикреплено окно (iOS 13+). |
screen | UIScreen | Экран, на котором отображается окно. При подключении внешнего дисплея — может отличаться от UIScreen.main. |
overrideUserInterfaceStyle | UIUserInterfaceStyle | Принудительная тема для всего окна: .unspecified, .light, .dark. |
tintColor | UIColor | Корневой tint-цвет приложения. |
isKeyWindow | Bool | Является ли окно активным (получает события). |
windowLevel | UIWindow.Level | Z-порядок: .normal, .alert, .statusBar, .overlay и др. Можно задавать кастомные (UIWindow.Level(rawValue: 100)). |
2.4. UIGestureRecognizer
Общий интерфейс для всех recognizers (UITapGestureRecognizer, UIPanGestureRecognizer, UIRotationGestureRecognizer и др.)
| Свойство | Тип | Описание |
|---|
state | UIGestureRecognizer.State | .possible, .began, .changed, .ended, .cancelled, .failed |
view | UIView? | Вьюха, к которой прикреплён recognizer. |
delegate | UIGestureRecognizerDelegate? | Управление конфликтами (gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:), gestureRecognizer(_:shouldBeRequiredToFailBy:) и др.) |
isEnabled | Bool | Включён/выключен. |
cancelsTouchesInView | Bool | Отменять ли touchesBegan у вьюхи при распознавании (по умолчанию true для tap/long press). |
delaysTouchesBegan, delaysTouchesEnded | Bool | Задержка передачи событий вьюхе (по умолчанию true для pan/swipe). |
require(toFail:) | func | Dependency chain: A требует, чтобы B провалился. |
✅ Пример:
let pan = UIPanGestureRecognizer()
let tap = UITapGestureRecognizer()
pan.require(toFail: tap)
2.5. UIControl
| Свойство | Тип | Описание |
|---|
isEnabled | Bool | Состояние: enabled/disabled (влияет на tintColor, alpha, взаимодействие). |
isSelected | Bool | Логическое выделение (например, segmented control, custom checkbox). |
isHighlighted | Bool | Временное состояние при нажатии (визуальная обратная связь). |
state | UIControl.State | Комбинация флагов: normal, highlighted, disabled, selected, focused, application, reserved. |
contentVerticalAlignment, contentHorizontalAlignment | UIControl.ContentVerticalAlignment, ...Horizontal... | Выравнивание содержимого внутри bounds. |
sendActions(for:) | func | Ручная отправка событий (touchUpInside, valueChanged, editingChanged и др.). |
addTarget(_:action:for:) | func | Подписка на события. |
allTargets, allControlEvents | Set<AnyHashable>, UIControl.Event | Диагностика: кто подписан и на что. |
| Свойство | Тип | Описание |
|---|
contentSize | CGSize | Размер «документа» внутри скролла. |
contentOffset | CGPoint | Текущая позиция прокрутки (отрицательные значения — overscroll/bounce). |
contentInset | UIEdgeInsets / NSDirectionalEdgeInsets | Отступы содержимого (например, под navigation bar). |
scrollIndicatorInsets | UIEdgeInsets | Отступы для индикаторов скролла (часто = contentInset). |
isScrollEnabled | Bool | Можно ли скроллить. |
isDirectionalLockEnabled | Bool | Блокировать вторую ось при начале скролла по одной (как в Safari). |
bounces | Bool | Разрешить «подпрыг» за краями. |
alwaysBounceVertical, alwaysBounceHorizontal | Bool | Показывать bounce даже если contentSize ≤ bounds. |
isPagingEnabled | Bool | Прокрутка строго по страницам (bounds.size). |
showsHorizontalScrollIndicator, showsVerticalScrollIndicator | Bool | Отображение индикаторов. |
indicatorStyle | UIScrollView.IndicatorStyle | .default, .black, .white. |
decelerationRate | UIScrollView.DecelerationRate | .normal, .fast, или кастомный CGFloat. |
delegate | UIScrollViewDelegate? | scrollViewDidScroll(_:), scrollViewWillBeginDragging(_:), scrollViewDidEndDecelerating(_:) и др. |
refreshControl | UIRefreshControl? | Pull-to-refresh (только для UITableView/UICollectionView, но хранится в scroll view). |
2.7. UITableView & UICollectionView — ключевые свойства, общие и отличия
| Свойство | Тип | UITableView | UICollectionView |
|---|
dataSource | protocol | UITableViewDataSource | UICollectionViewDataSource |
delegate | protocol | UITableViewDelegate | UICollectionViewDelegate |
prefetchDataSource | protocol? | UITableViewDataSourcePrefetching? | UICollectionViewDataSourcePrefetching? |
estimatedRowHeight, estimatedSectionHeaderHeight, estimatedSectionFooterHeight | CGFloat | Для авто-расчёта высот (при rowHeight = UITableView.automaticDimension) | — |
rowHeight | CGFloat | Фиксированная высота строки | — |
sectionHeaderHeight, sectionFooterHeight | CGFloat | Аналогично | — |
style | UITableView.Style | .plain, .grouped, .insetGrouped | — |
separatorStyle | UITableViewCell.SeparatorStyle | .none, .singleLine, .singleLineEtched | — |
collectionViewLayout | UICollectionViewLayout | — | Обязательное свойство. Стандартные: UICollectionViewFlowLayout, NSCollectionLayoutSection (Compositional). |
isPrefetchingEnabled | Bool | Устарело (в iOS 10 введено, но сейчас управляется через prefetchDataSource) | true по умолчанию. |
allowsSelection, allowsMultipleSelection | Bool | | |
allowsSelectionDuringEditing | Bool | | |
indexPathsForSelectedItems | [IndexPath] | indexPathForSelectedRow | Массив (multi-select). |
visibleCells | [UITableViewCell] / [UICollectionViewCell] | Все видимые ячейки. | |
indexPathsForVisibleItems | [IndexPath] | indexPathsForVisibleRows | |
contentOffsetAdjustmentBehavior | UIScrollView.ContentOffsetAdjustmentBehavior (iOS 15+) | .automatic, .never — поведение при изменении contentInset | То же (наследуется от UIScrollView). |
🔁 Compositional Layout (iOS 13+) — ключевые объекты:
NSCollectionLayoutSection (orthogonalScrollingBehavior, contentInsets, interGroupSpacing)
NSCollectionLayoutGroup (.horizontal, .vertical, .custom)
NSCollectionLayoutItem (size: NSCollectionLayoutSize(widthDimension: ..., heightDimension: ...))
NSCollectionLayoutSize: .absolute(CGFloat), .estimated(CGFloat), .fractionalWidth(CGFloat), .fractionalHeight(CGFloat)
NSDirectionalEdgeInsets — для отступов.
2.8. UITraitCollection — полный список актуальных traits (iOS 18)
| Trait | Тип | Возможные значения | Комментарий |
|---|
userInterfaceStyle | UIUserInterfaceStyle | .unspecified, .light, .dark | Может отличаться от системного (если overrideUserInterfaceStyle задан). |
horizontalSizeClass, verticalSizeClass | UIUserInterfaceSizeClass | .unspecified, .compact, .regular | Например, iPhone в portrait: h=.compact, v=.regular; iPad split view: h=.compact, v=.regular. |
displayScale | CGFloat | 1.0, 2.0, 3.0 | Аналог UIScreen.scale, но для окна/VC. |
displayCornerRadius | CGFloat | 0.0, 13.0, 20.0, 30.0 | Радиус скругления экрана (для адаптации corner radius в UI). |
preferredContentSizeCategory | UIContentSizeCategory | .extraSmall, .small, .medium, .large, .extraLarge, .extraExtraLarge, .accessibilityMedium, ..., .accessibilityExtraExtraExtraLarge | Динамические типы (UIFontMetrics). |
legibilityWeight | UIUserInterfaceLegibilityWeight | .unspecified, .regular, .bold | Для повышения контрастности (например, на lock screen). |
forceTouchCapability | UIForceTouchCapability | .unknown, .available, .unavailable | 3D Touch (устарел, но property остался). |
layoutDirection | UIUserInterfaceLayoutDirection | .leftToRight, .rightToLeft | Определяет leading/trailing. |
userInterfaceLevel | UIUserInterfaceLevel | .unspecified, .base, .elevated | Для адаптации под elevated UI (например, alert поверх основного контента). |
accessibilityContrast | UIAccessibilityContrast | .unspecified, .normal, .high | Высококонтрастный режим (Accessibility → Display). |
accessibilityDifferentiateWithoutColor | Bool | | Использование форм/текстур вместо цвета. |
accessibilityReduceTransparency | Bool | | Отключение blur-эффектов. |
accessibilityReduceMotion | Bool | | Отключение анимаций. |
preferredLanguages | [String] | ["ru-RU", "en-GB", "en"] | Языки в порядке предпочтения (из Settings → Language). |
accessibilityIgnoresInvertColors | Bool | | Не инвертировать цвета при Smart Invert. |
elevation | UIWindowScene.Elevation? (iOS 16+) | .primary, .secondary, .tertiary | Для сцен: main app, sheet, popover. |
containerIdiom | UIUserInterfaceIdiom | .unspecified, .phone, .pad, .tv, .carPlay, .mac, .vision | Идентификатор устройства, не зависит от размера окна (в отличие от size class). |
✅ Пример использования:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
updateColors()
}
if traitCollection.preferredContentSizeCategory != previousTraitCollection?.preferredContentSizeCategory {
updateFonts()
}
}
📘 UI-компоненты
- Accessibility Attributes
- Core Animation Layer Properties (в контексте UIKit)
Фокус — на публичных, часто используемых и диагностически важных свойствах, включая ограничения поведения, взаимодействие с Auto Layout, особенности рендеринга и совместимость с SwiftUI/UIKit interoperability.
3.1. UILabel
| Свойство | Тип | Описание | Особенности |
|---|
text | String? | Текст (plain). | При nil — пустая строка. Не интерпретирует HTML. |
attributedText | NSAttributedString? | Форматированный текст. Приоритет выше, чем text. | Поддержка NSFontAttributeName, NSForegroundColorAttributeName, NSShadowAttributeName, NSLinkAttributeName, NSBaselineOffsetAttributeName, NSKernAttributeName, NSLigatureAttributeName, NSUnderlineStyleAttributeName, NSStrikethroughStyleAttributeName, NSBaselineOffsetAttributeName, NSTextEffectAttributeName, NSAttachmentAttributeName, NSObliquenessAttributeName, NSExpansionAttributeName. |
font | UIFont? | Шрифт. | По умолчанию UIFont.systemFont(ofSize: 17). Использует UIFontMetrics.default.scaledFont(for:) для динамических типов (если adjustsFontForContentSizeCategory = true). |
textColor | UIColor? | Цвет текста. | По умолчанию .label (адаптивный под тему). |
textAlignment | NSTextAlignment | .left, .center, .right, .justified, .natural. | .natural → зависит от layoutDirection. |
lineBreakMode | NSLineBreakMode | .byWordWrapping, .byCharWrapping, .byClipping, .byTruncatingHead, .byTruncatingTail, .byTruncatingMiddle. | Влияет на отображение при numberOfLines = 1 и недостатке ширины. |
numberOfLines | Int | Количество строк (0 = бесконечно). | При 1 и lineBreakMode = .byTruncatingTail — … в конце. |
adjustsFontSizeToFitWidth | Bool | Автоуменьшение шрифта до minimumScaleFactor. | Только при numberOfLines = 1. |
minimumScaleFactor | CGFloat | Минимальный масштаб шрифта (относительно font.pointSize). | По умолчанию 0.5. |
baselineAdjustment | UIBaselineAdjustment | .alignBaselines, .alignCenters, .none. | Для выравнивания базовой линии при изменении размера. |
highlightedTextColor | UIColor? | Цвет при isHighlighted = true. | Редко используется (только если isUserInteractionEnabled = true и есть touches). |
isHighlighted | Bool | Визуальное выделение (меняет textColor → highlightedTextColor). | |
isEnabled | Bool | Состояние (влияет на textColor, но не блокирует ввод — label не интерактивна по умолчанию). | |
shadowColor, shadowOffset | UIColor?, CGSize | Тень текста. | Простая, без blur (в отличие от layer shadow). |
adjustsFontForContentSizeCategory | Bool | Автомасштабирование шрифта под Accessibility → Larger Text. | Требует использования UIFontMetrics. |
textRect(forBounds:limitedToNumberOfLines:) | func | Расчёт bounding box текста без отрисовки. | Полезно для кастомных draw(_:). |
preferredMaxLayoutWidth | CGFloat | Ширина, при которой пересчитывается высота (для multi-line в Auto Layout без width constraint). | Устарело в пользу intrinsicContentSize + constraints, но иногда требуется для UITableViewCell в iOS ≤11. |
⚠️ Ограничения:
UILabel не поддерживает интерактивные атрибуты в NSAttributedString (например, NSLinkAttributeName не кликабелен). Для этого — UITextView с isEditable = false, isSelectable = true.
- При
attributedText и adjustsFontSizeToFitWidth — поведение не определено (Apple не гарантирует работу).
3.2. UITextField
| Свойство | Тип | Описание |
|---|
text, attributedText | String?, NSAttributedString? | Содержимое. Приоритет: attributedText > text. |
placeholder, attributedPlaceholder | String?, NSAttributedString? | Текст-заполнитель. Приоритет: attributedPlaceholder. |
font, textColor, textAlignment, minimumFontSize | — | Аналогично UILabel. |
clearButtonMode | UITextField.ViewMode | .never, .whileEditing, .unlessEditing, .always. |
leftView, rightView | UIView? | Кастомные вьюхи слева/справа (например, иконки). |
leftViewMode, rightViewMode | UITextField.ViewMode | Когда показывать (.always, .unlessEditing, .whileEditing, .never). |
inputView | UIView? | Замена клавиатуры (например, UIPickerView). |
inputAccessoryView | UIView? | Панель над клавиатурой (часто — Done/Next toolbar). |
keyboardType | UIKeyboardType | .default, .asciiCapable, .numbersAndPunctuation, .URL, .numberPad, .phonePad, .namePhonePad, .emailAddress, .decimalPad, .twitter, .webSearch, .asciiCapableNumberPad. |
keyboardAppearance | UIKeyboardAppearance | .default, .dark, .light. |
returnKeyType | UIReturnKeyType | .default, .go, .google, .join, .next, .route, .search, .send, .yahoo, .done, .emergencyCall. |
enablesReturnKeyAutomatically | Bool | Деактивировать Return, пока text пусто. |
isSecureTextEntry | Bool | Скрытие ввода (звёздочки/кружки). Сбрасывает attributedText. |
clearsOnBeginEditing | Bool | Очищать текст при фокусе. |
clearsOnInsertion | Bool | Очищать при paste/диктовке (редко). |
delegate | UITextFieldDelegate? | textFieldShouldBeginEditing(_:), textFieldDidBeginEditing(_:), textFieldShouldEndEditing(_:), textFieldDidEndEditing(_:), textField(_:shouldChangeCharactersIn:replacementString:), textFieldShouldClear(_:), textFieldShouldReturn(_:). |
selectedTextRange | UITextRange? | Текущее выделение (если isEditable = true). |
markedTextRange | UITextRange? | Диапазон составного ввода (например, при вводе кандзи). |
allowsEditingTextAttributes | Bool | Разрешить форматирование через paste (если attributedText используется). |
typingAttributes | [NSAttributedString.Key : Any] | Атрибуты для нового ввода (курсора). |
⚠️ Важно:
UITextField не поддерживает multi-line. Для этого — UITextView.
- При
isSecureTextEntry = true — secureTextEntry сбрасывает font на системный (без кастомных шрифтов).
UITextField наследует UIControl, но не вызывает sendActions(for: .valueChanged) — только .editingChanged.
3.3. UITextView
| Свойство | Тип | Описание |
|---|
isEditable | Bool | Редактируемость. При false — не появляется клавиатура. |
isSelectable | Bool | Возможность выделять и копировать текст (даже при isEditable = false). |
dataDetectorTypes | UIDataDetectorTypes | .phoneNumber, .link, .address, .calendarEvent, .shipmentTrackingNumber, .flightNumber, .lookupSuggestion, .all, .none. |
textContainerInset | UIEdgeInsets | Отступы текста от границ вьюхи. |
textContainer.lineFragmentPadding | CGFloat | Горизонтальный отступ строк (по умолчанию 5.0). |
isScrollEnabled | Bool | Можно ли скроллить (наследуется от UIScrollView). |
linkTextAttributes | [NSAttributedString.Key : Any] | Атрибуты для ссылок (при isSelectable = true). |
allowsEditingTextAttributes | Bool | Поддержка копирования форматирования. |
typingAttributes | [NSAttributedString.Key : Any] | Атрибуты ввода. |
selectedRange | NSRange | Текущее выделение. |
delegate | UITextViewDelegate? | textViewShouldBeginEditing(_:), textViewDidBeginEditing(_:), textViewShouldEndEditing(_:), textViewDidEndEditing(_:), textView(_:shouldChangeTextIn:replacementText:), textViewDidChange(_:), textViewDidChangeSelection(_:), textView(_:shouldInteractWith:in:interaction:). |
inputDelegate | UITextInputDelegate? | Низкоуровневый протокол для кастомных input methods. |
✅ Для неинтерактивного rich text с ссылками используйте:
textView.isEditable = false
textView.isSelectable = true
textView.dataDetectorTypes = .link
3.4. UIImageView
| Свойство | Тип | Описание |
|---|
image | UIImage? | Изображение (растровое или PDF). |
highlightedImage | UIImage? | Изображение при isHighlighted = true. |
isHighlighted | Bool | Переключает image ↔ highlightedImage. |
isAnimating | Bool | Идёт ли анимация (для animatedImage). |
animationImages | [UIImage]? | Кадры анимации. |
highlightedAnimationImages | [UIImage]? | Кадры при isHighlighted. |
animationDuration | TimeInterval | Длительность одного цикла анимации. |
animationRepeatCount | Int | Количество повторов (0 = бесконечно). |
startAnimating(), stopAnimating() | func | Управление анимацией. |
tintColor | UIColor? | Применяется, если image?.renderingMode = .alwaysTemplate. |
⚠️ Ограничения:
UIImageView не кэширует изображения — это ответственность UIImage.
- При
contentMode = .scaleAspectFill + clipsToBounds = true — стандартный способ «cover image».
- Анимация через
animationImages — не hardware-accelerated (избегать для >10 кадров или высокого FPS).
Начиная с iOS 15, UIButton использует UIButton.Configuration (рекомендуемый путь). Ниже — свойства традиционного (UIButton(type:)) и нового (configuration) API.
Традиционный API (устаревший, но поддерживаемый)
| Свойство | Тип | Описание |
|---|
setTitle(_:for:), title(for:) | String?, UIControl.State | Заголовок для состояния. |
setImage(_:for:), image(for:) | UIImage?, UIControl.State | Иконка слева от текста. |
setBackgroundImage(_:for:), backgroundImage(for:) | UIImage?, UIControl.State | Фон (растягивается по contentEdgeInsets). |
setAttributedTitle(_:for:) | NSAttributedString? | Форматированный заголовок. |
titleEdgeInsets, imageEdgeInsets, contentEdgeInsets | UIEdgeInsets | Отступы. contentEdgeInsets сдвигает всю «контент-область». |
titleLabel, imageView | UILabel?, UIImageView? | Прямой доступ к подвьюхам (не рекомендуется менять constraints). |
| Конфигурация | Описание |
|---|
plain() | Простая кнопка (как UIButton(type: .system)). |
tinted() | С заполнением и тенью (акцентная). |
filled() | Сплошная заливка (по умолчанию с title, image). |
bordered(), borderedProminent(), borderedTinted() | С рамкой. |
gray(), destructive() | Предустановленные стили. |
Свойства UIButton.Configuration | Тип | Описание |
|---|
title, attributedTitle | String?, NSAttributedString? | |
image, alternativeImage | UIImage? | alternativeImage — при .selected или .highlighted. |
baseBackgroundColor, baseForegroundColor | UIColor? | Базовые цвета (адаптивные под тему). |
background | UIButton.Configuration.Background | .filled, .tinted, .bordered, кастомный градиент. |
cornerStyle | UIButton.Configuration.CornerStyle | .dynamic, .fixed(CGFloat), .capsule, .medium, .large. |
buttonSize | UIButton.Configuration.Size | .mini, .small, .medium, .large. |
titleAlignment, imagePlacement | UIControl.ContentHorizontalAlignment, NSDirectionalRectEdge | Позиционирование. |
showsActivityIndicator | Bool | Показывать UIActivityIndicatorView вместо image (например, при загрузке). |
subtitle, attributedSubtitle | String?, NSAttributedString? | Дополнительный текст (под заголовком). |
contentInsets | NSDirectionalEdgeInsets | Отступы (directional). |
✅ Преимущества Configuration API:
- Автоматическая поддержка Dynamic Type, Dark Mode, Reduce Motion.
- Нет необходимости управлять состояниями вручную — система сама переключает
title/image/background.
- Поддержка
showsActivityIndicator без кастомной логики.
3.6. UISwitch
| Свойство | Тип | Описание |
|---|
isOn | Bool | Состояние. |
onTintColor, thumbTintColor | UIColor? | Цвет фона (включено) и «ползунка». |
onImage, offImage | UIImage? | Иконки внутри ползунка (редко используются). |
setEnabled(_:animated:) | func | Анимированное изменение состояния disabled. |
⚠️ iOS 14+ игнорирует onTintColor, если включён Accessibility → Increase Contrast — используется системный цвет.
3.7. UISlider
| Свойство | Тип | Описание |
|---|
value | Float | Текущее значение. |
minimumValue, maximumValue | Float | Диапазон. |
minimumValueImage, maximumValueImage | UIImage? | Иконки на концах. |
minimumTrackTintColor, maximumTrackTintColor, thumbTintColor | UIColor? | Цвета треков и ползунка. |
isContinuous | Bool | Отправлять valueChanged при каждом движении (true) или только по окончании (false). |
setThumbImage(_:for:) | func | Кастомный thumb для состояния. |
minimumTrackImage, maximumTrackImage | UIImage? | Замена track’ов на изображения. |
3.8. UIProgressView
| Свойство | Тип | Описание |
|---|
progress | Float | 0.0–1.0. |
progressViewStyle | UIProgressView.Style | .default, .bar. |
progressTintColor, trackTintColor | UIColor? | Цвет заполнения и фона. |
progressImage, trackImage | UIImage? | Кастомные изображения (редко). |
setProgress(_:animated:) | func | Анимированное обновление. |
3.9. UIActivityIndicatorView
| Свойство | Тип | Описание |
|---|
style | UIActivityIndicatorView.Style | .medium, .large, .white, .gray, .whiteLarge (устаревшие), .medium/.large — preferred. |
color | UIColor? | Цвет индикатора (переопределяет стиль). |
hidesWhenStopped | Bool | Автоскрытие при stopAnimating(). |
startAnimating(), stopAnimating() | func | Управление. |
isAnimating | Bool | Состояние. |
✅ iOS 13+: UIActivityIndicatorView автоматически адаптирует цвет под тему, если color не задан.
3.10. UIStackView
| Свойство | Тип | Описание |
|---|
axis | NSLayoutConstraint.Axis | .horizontal, .vertical. |
distribution | UIStackView.Distribution | .fill, .fillEqually, .fillProportionally, .equalSpacing, .equalCentering. |
alignment | UIStackView.Alignment | .fill, .leading, .trailing, .center, .firstBaseline, .lastBaseline. |
spacing | CGFloat | Расстояние между arrangedSubviews. |
arrangedSubviews | [UIView] | Дочерние вьюхи, управляемые stack’ом. |
isLayoutMarginsRelativeArrangement | Bool | Учитывать layoutMargins при расчёте позиций. |
addArrangedSubview(_:), removeArrangedSubview(_:) | func | Добавление/удаление (не удаляет из иерархии — только из layout’а). |
⚠️ Важно:
UIStackView не управляет размерами arrangedSubviews — он только распределяет свободное пространство.
- При
distribution = .fillEqually — все вьюхи получают одинаковый length (width при horizontal, height при vertical).
- При
spacing = 0 и отсутствии constraints — возможны наложения.
3.11. Accessibility Attributes (универсальные для UIAccessibility)
Свойство (через view.accessibilityX) | Тип | Описание |
|---|
accessibilityLabel | String? | Краткое описание элемента («Кнопка отправки», «Имя пользователя»). |
accessibilityHint | String? | Дополнительное пояснение («Дважды нажмите для отправки»). |
accessibilityValue | String? | Текущее значение («50%», «3 из 10»). |
accessibilityTraits | UIAccessibilityTraits | Битовая маска: .button, .link, .searchField, .image, .selected, .playsSound, .keyboardKey, .staticText, .summaryElement, .updatesFrequently, .startsMediaSession, .adjustable, .allowsDirectInteraction, .causesPageTurn, .tabBar. |
accessibilityFrame | CGRect | Позиция в screen coordinates (переопределяет frame для VoiceOver). |
accessibilityFrameInContainerSpace | CGRect | То же, но в координатах контейнера (лучше для rotation). |
accessibilityLanguage | String? | Язык содержимого (например, "ru-RU"), если отличается от локали приложения. |
accessibilityElements | [Any]? | Кастомный порядок обхода (для контейнеров). |
accessibilityContainerType | UIAccessibilityContainerType | .automatic, .semanticGroup, ..landmark, ..list, ..row, ..tree. |
accessibilityRespondsToUserInteraction | Bool | Элемент реагирует на касания (например, UILabel с isUserInteractionEnabled = true). |
accessibilityIgnoresInvertColors | Bool | Не инвертировать при Smart Invert. |
accessibilityViewIsModal | Bool | Ограничивать VoiceOver текущим окном (например, для alert’ов). |
shouldGroupAccessibilityChildren | Bool | Группировать дочерние элементы в один узел (например, для ячеек таблицы). |
✅ Проверка:
- Используйте Accessibility Inspector в Xcode (
Xcode → Open Developer Tool → Accessibility Inspector).
UIAccessibility.post(notification:argument:) — для announce’а изменений (например, UIAccessibility.Notification.layoutChanged).
3.12. Core Animation Layer Properties (через view.layer)
| Свойство | Тип | Описание |
|---|
cornerRadius | CGFloat | Скругление углов. При masksToBounds = true — обрезка содержимого. |
borderWidth, borderColor | CGFloat, CGColor? | Рамка. |
shadowColor, shadowOpacity, shadowOffset, shadowRadius | CGColor?, Float, CGSize, CGFloat | Тень (hardware-accelerated, если shouldRasterize = false). |
masksToBounds | Bool | Эквивалент clipsToBounds. |
shouldRasterize | Bool | Кэшировать layer как bitmap (ускоряет сложные иерархии, но увеличивает память и может вызывать artefact’ы при масштабировании). |
rasterizationScale | CGFloat | Должен быть = UIScreen.main.scale при shouldRasterize = true. |
drawsAsynchronously | Bool | Рендеринг draw(_:) в фоновом потоке (только для CALayerDelegate). |
contents | Any? | Прямой bitmap (редко — обычно через UIImage). |
contentsGravity | CALayerContentsGravity | Поведение contents при изменении bounds (аналог contentMode). |
zPosition | CGFloat | Z-смещение (влияет на порядок отрисовки внутри одного superview). |
anchorPoint | CGPoint | Точка привязки transform’ов и позиционирования (по умолчанию {0.5, 0.5}). |
anchorPointZ | CGFloat | Z-компонента anchor point’а. |
sublayerTransform | CATransform3D | Transform для всех sublayers. |
⚠️ Производительность:
- Тень +
masksToBounds = true → offscreen rendering (дорого!). Решение: отдельный shadow-layer без masksToBounds.
shouldRasterize = true + анимация transform/alpha → перерастеризация кадр за кадром (avoid!).
📘 Модальные представления, Scene Lifecycle, Фоновые режимы, Privacy & Permissions
Фокус — на публичных API, runtime-состояниях, диагностируемых атрибутах и поведениях, критичных для корректной работы приложения в реальных условиях (включая edge cases: отклонение разрешений, переходы между foreground/background, adaptive interfaces).
4.1. Модальные представления: UIPresentationController, UISheetPresentationController (iOS 15+), UIAdaptivePresentationController
Начиная с iOS 13, модальный показ управляется через presentation controller, а не напрямую через UIViewController.
Конфигурация задаётся через modalPresentationStyle, modalTransitionStyle, и (для sheet’ов) — через sheetPresentationController.
4.1.1. Основные UIModalPresentationStyle и их поведение
| Значение | Поддержка | Поведение | Особенности |
|---|
.fullScreen | iOS 2+ | Занимает весь экран, заменяет rootViewController визуально. | Принудительно затемняет background. Не анимируется снизу — всплывает/исчезает. |
.pageSheet | iOS 13+ | Лист «страницы»: на iPhone — почти full screen, на iPad — центрированная панель с фоном. | Ширина: ~540pt на iPad, ~full на iPhone. Поддерживает pull-to-dismiss. |
.formSheet | iOS 3.2+ | Центрированная форма (устарела на iPhone — ведёт себя как .pageSheet). | На iPad: фиксированный размер (~540×620 pt). |
.overFullScreen | iOS 3+ | Поверх всего, без затемнения фона. | Используется для прозрачных оверлеев (например, кастомные alert’ы). Требует modalTransitionStyle = .crossDissolve для плавности. |
.overCurrentContext | iOS 7+ | Поверх текущего VC (не root), с затемнением. | Background VC остаётся в viewDidAppear, но неактивен. |
.popover | iPad-only | Всплывающее окно с anchor. | Требует popoverPresentationController?.sourceView и sourceRect. При tap outside — автоматически dismiss (если popoverPresentationController?.passthroughViews == nil). |
.automatic | iOS 13+ | Автовыбор: на iPhone → .fullScreen, на iPad + compact height → .pageSheet, иначе — .formSheet. | Рекомендуемое значение по умолчанию. |
4.1.2. UISheetPresentationController (iOS 15+)
Появляется, когда modalPresentationStyle = .pageSheet и VC поддерживает adaptive sheet (по умолчанию — да).
| Свойство | Тип | Описание |
|---|
isPrefersGrabberVisible | Bool | Показывать «ручку» сверху sheet’а (по умолчанию false). |
largestUndimmedDetentIdentifier | UISheetPresentationController.Detent.Identifier? | До какого detent’а не затемнять фон: .medium, .large, .all, nil. |
detents | [UISheetPresentationController.Detent] | Возможные высоты: |
.medium() — ~½ экрана
.large() — почти full height
.custom(resolver:) — кастомная высота в точках |
| selectedDetentIdentifier | UISheetPresentationController.Detent.Identifier? | Текущая высота (read-only). |
| prefersScrollingExpandsWhenScrolledToEdge | Bool | Sheet автоматически раскрывается при скролле вверх (например, как в Mail). |
| prefersEdgeAttachedInCompactHeight | Bool | На iPhone в landscape — прикреплять к краю, а не центрировать. |
| widthFollowsPreferredContentSizeWhenEdgeAttached | Bool | При prefersEdgeAttachedInCompactHeight = true — ширина = preferredContentSize.width. |
✅ Пример адаптивного sheet’а:
if let sheet = vc.sheetPresentationController {
sheet.detents = [.medium(), .large()]
sheet.largestUndimmedDetentIdentifier = .medium
sheet.isPrefersGrabberVisible = true
}
4.1.3. UIAdaptivePresentationController и адаптация
Контроллер, управляющий изменением presentation style при смене traits (например, iPad → split view).
| Свойство | Тип | Описание |
|---|
delegate | UIAdaptivePresentationControllerDelegate? | |
adaptivePresentationStyle(for:) — вернуть новый стиль при изменении traits
presentationController(_:viewControllerAdaptivePresentationStyleFor:) — то же, но per-VC
presentationController(_:willPresentWithAdaptiveStyle:transitionCoordinator:) — перед адаптацией |
| overrideTraitCollection | UITraitCollection? | Принудительно задать traits для presented VC (например, .init(horizontalSizeClass: .compact) для iPad sheet’а). |
⚠️ Если не реализовать делегат — при повороте iPad sheet может неожиданно стать .fullScreen.
4.2. Scene Lifecycle (UIScene, UIWindowScene, UISceneDelegate)
С iOS 13+ приложение может иметь несколько сцен (multiple windows на iPad, external display, Handoff, Siri Shortcuts).
4.2.1. Ключевые объекты
| Объект | Роль |
|---|
UISceneConfiguration | Описывает сцену в Info.plist → UIApplicationSceneManifest. Имя, role (.windowApplication, .windowExternalDisplay), storyboard/class. |
UISceneSession | Runtime-сессия сцены: identifier: String, role: UIScene.Role, configuration: UISceneConfiguration, state: UIScene.State. |
UIScene | Абстрактный класс. Реализации: UIWindowScene, UIWindowlessScene (редко). |
UIWindowScene | Сцена с окном. Имеет windows: [UIWindow], screen: UIScreen, activationState: UIScene.ActivationState. |
UISceneDelegate | Аналог UIApplicationDelegate, но для сцены. |
4.2.2. UIScene.ActivationState
| Значение | Описание | Примечание |
|---|
.unattached | Сцена создана, но не привязана к экрану. | После scene(_:willConnectTo:options:), до sceneDidDisconnect(_:). |
.foregroundInactive | На переднем плане, но неактивна (например, Control Center открыт). | Аналог UIApplication.State.inactive. |
.foregroundActive | Полностью активна, получает события. | Аналог UIApplication.State.active. |
.background | В фоне. | Аналог UIApplication.State.background. |
.discarded — нет такого значения. Сцена уничтожается через sceneDidDisconnect(_:). | | |
4.2.3. События UISceneDelegate
| Метод | Когда вызывается | Аналог в UIApplicationDelegate |
|---|
scene(_:willConnectTo:options:) | При создании сцены | application(_:willFinishLaunchingWithOptions:) + часть didFinishLaunching |
sceneDidDisconnect(_:) | При уничтожении сцены | applicationWillTerminate(_:) (но не всегда — сцена может быть background’ом) |
sceneDidBecomeActive(_:) | → .foregroundActive | applicationDidBecomeActive(_:) |
sceneWillResignActive(_:) | → .foregroundInactive | applicationWillResignActive(_:) |
sceneDidEnterBackground(_:) | → .background | applicationDidEnterBackground(_:) |
sceneWillEnterForeground(_:) | → .foregroundInactive | applicationWillEnterForeground(_:) |
scene(_:openURLContexts:) | При открытии URL | application(_:open:options:) |
scene(_:continue:) | Handoff | application(_:continue:restorationHandler:) |
scene(_:didUpdate:) | Изменение traits (size class, scale и др.) | traitCollectionDidChange(_:) у корневого VC |
⚠️ Важно:
application(_:didFinishLaunchingWithOptions:) всё ещё вызывается, но не должен создавать UI — только инициализировать глобальное состояние.
- Все window’ы создаются в
scene(_:willConnectTo:options:), а не в AppDelegate.
4.3. Фоновые режимы: UIBackgroundModes и соответствующие API
Система разрешает ограниченное выполнение в фоне только при наличии entitlement’ов, объяснения в App Store Connect и корректного использования API.
4.3.1. Поддерживаемые режимы и требования
UIBackgroundModes | Entitlement | Обязательное API | Ограничения | Макс. время |
|---|
audio | com.apple.developer.audio.background | AVAudioSession.setCategory(.playback, options: .mixWithOthers) + play() | Должен проигрывать или записывать аудио. | ∞ (пока аудио идёт) |
location | com.apple.developer.location.background | CLLocationManager.startUpdatingLocation() или startMonitoringSignificantLocationChanges() | Точность ≤ kCLLocationAccuracyHundredMeters для significant. | ∞ (но с пониженной частотой) |
fetch | — | UIApplication.shared.setMinimumBackgroundFetchInterval(_:) и реализация application(_:performFetchWithCompletionHandler:) | Запускается по эвристике (раз в несколько часов). | ~30 сек |
remote-notification | remote-notification в APS | content-available: 1 в payload + реализация application(_:didReceiveRemoteNotification:fetchCompletionHandler:) | Не гарантируется доставка; не для interactive notifications. | ~30 сек |
bluetooth-central | com.apple.developer.bluetooth-central | CBCentralManager с CBCentralManagerOptionRestoreIdentifierKey | Должен подключаться/сканировать устройства. | ∞ (при подключении) |
bluetooth-peripheral | com.apple.developer.bluetooth-peripheral | CBPeripheralManager с restore identifier | Должен рекламировать сервисы. | ∞ (при подключении) |
background-processing | com.apple.developer.background-modes + BGTaskScheduler | BGProcessingTaskRequest, BGAppRefreshTaskRequest (iOS 13+) | Требует registration в application(_:didFinishLaunchingWithOptions:). | До 30 мин (processing), ~10 сек (refresh) |
voip | ❌ Удалено в iOS 8+ | — | Недоступно. Используйте PushKit + CallKit. | — |
continuous-waveform-processing | com.apple.developer.hearable.capabilities.raw-streaming | HAAudioStream (Hearing Aid API) | Только для устройств поддержки слуха. | ∞ |
app-events | com.apple.developer.device-information.user-assigned-device-name | NSUserActivity с requiredUserInfoKeys | Для Shortcuts/Intents. | ∞ (но execution — по запросу) |
4.3.2. Явные фоновые задачи (beginBackgroundTask)
var bgTask: UIBackgroundTaskIdentifier = .invalid
func startWork() {
bgTask = UIApplication.shared.beginBackgroundTask {
endWork()
}
uploadData {
endWork()
}
}
func endWork() {
UIApplication.shared.endBackgroundTask(bgTask)
bgTask = .invalid
}
| Параметр | Значение |
|---|
| Макс. время | ~30 секунд (гарантировано), до ~180 сек в редких случаях (iOS 13+) |
| Таймер | UIApplication.shared.backgroundTimeRemaining (в секундах, Double) |
| Отладка | В Xcode: Debug → Simulate Background Fetch, Debug → Trigger Background Task Expiration |
⚠️ Не используйте beginBackgroundTask для замены fetch/processing — App Review отклонит.
4.3.3. BGTaskScheduler (iOS 13+, рекомендуемо вместо fetch)
Регистрация в application(_:didFinishLaunchingWithOptions:):
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.example.refresh",
using: nil
) { task in
self.handleAppRefresh(task as! BGAppRefreshTask)
}
Запуск:
let request = BGAppRefreshTaskRequest(identifier: "com.example.refresh")
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
try? BGTaskScheduler.shared.submit(request)
| Тип задачи | Мин. интервал | Использование |
|---|
BGAppRefreshTask | 15 минут | Обновление контента (аналог fetch) |
BGProcessingTask | 1 день (эвристически) | Тяжелые операции (очистка кэша, sync) |
✅ Преимущества:
- Система сама оптимизирует энергопотребление.
- Поддержка
requiresNetworkConnectivity, requiresExternalPower.
4.4. Privacy & Permissions: Состояния и диагностика
С iOS 14+ многие разрешения имеют ограниченный доступ (.limited) или временный (.provisional).
4.4.1. Общая схема проверки
switch status {
case .notDetermined:
manager.requestAuthorization()
case .restricted:
case .denied:
case .authorized:
case .limited:
case .provisional:
}
4.4.2. Конкретные менеджеры и их статусы
| API | Статусы (CLAuthorizationStatus, PHAuthorizationStatus и др.) | Особенности |
|---|
CLLocationManager.authorizationStatus | .notDetermined, .restricted, .denied, .authorizedAlways, .authorizedWhenInUse, .authorized (iOS 14+, deprecated alias) | При .authorizedWhenInUse + запрос на .always — второй запрос не покажется, пока пользователь не перейдёт в Settings. |
PHPhotoLibrary.authorizationStatus(for: .readWrite) | .notDetermined, .restricted, .denied, .authorized, .limited | .limited → использовать PHPickerViewController (не UIImagePickerController). |
AVCaptureDevice.authorizationStatus(for: .video) | .notDetermined, .restricted, .denied, .authorized | Отдельно для .microphone. |
CTCallCenter / CXCallObserver | — | Не требует entitlement’ов, но данные ограничены (только incoming/outgoing state, без номеров). |
UserNotifications (UNUserNotificationCenter.current().getNotificationSettings) | .notDetermined, .denied, .authorized, .provisional, .ephemeral | .provisional — разрешение без запроса (только для non-interruptive уведомлений). .ephemeral — для Siri/Shortcuts (временные). |
Bluetooth (CBCentralManager.authorization) | .notDetermined, .restricted, .denied, .allowedAlways | Проверяется через CBManager.authorization. |
HealthKit (HKHealthStore.authorizationStatus(for:)) | .notDetermined, .sharingDenied, .sharingAuthorized | Отдельно для read/write per-типу (.quantityType(forIdentifier: .stepCount)). |
Speech (SFSpeechRecognizer.authorizationStatus) | .notDetermined, .restricted, .denied, .authorized | Требует NSSpeechRecognitionUsageDescription. |
NFC (NFCNDEFReaderSession.readingAvailable) | — | readingAvailable == false → нет железа или отключено в Settings. |
4.4.3. Диагностика через Settings URL
if status == .denied {
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
}
⚠️ App Review требует:
- Не показывать запрос разрешения при первом запуске без контекста.
- Перед запросом — показать «rationale screen» с объяснением зачем нужно разрешение.
- При
.denied — не спамить запросами, а вести в Settings.
📘 Сетевые операции, геолокация, Core Data, Keychain, App Extensions
Фокус — на публичных, стабильных, диагностируемых свойствах и конфигурациях, критичных для production-приложений:
- Устойчивость к ошибкам
- Энергоэффективность
- Соответствие требованиям App Store Review
- Возможность мониторинга и отладки
5.1. URLSession: Сессии, задачи, метрики, кэширование
5.1.1. Типы сессий и их назначение
| Тип | Создание | Использование | Особенности |
|---|
| default | URLSession(configuration: .default) | Обычные запросы (UI, API) | Использует глобальный кэш, куки, credentials. |
| ephemeral | URLSession(configuration: .ephemeral) | Приватный режим (инкогнито) | Не сохраняет куки, кэш, credentials между сессиями. |
| background | URLSession(configuration: .background(withIdentifier: "id")) | Загрузки/выгрузки в фоне | Должна быть сильной ссылкой на URLSessionDelegate. Выживает после termination. Требует entitlement com.apple.developer.networking.background-session. |
| custom | URLSessionConfiguration() + настройка | Тонкая настройка | Можно задать timeoutIntervalForRequest, httpMaximumConnectionsPerHost, tlsMinimumSupportedProtocolVersion, networkServiceType, и др. |
5.1.2. Свойства URLSessionConfiguration
| Свойство | Тип | Описание |
|---|
httpAdditionalHeaders | [String : Any]? | Заголовки по умолчанию для всех задач. |
timeoutIntervalForRequest | TimeInterval | Таймаут на установку соединения + отправку/получение данных (по умолчанию 60 сек). |
timeoutIntervalForResource | TimeInterval | Макс. время выполнения задачи (по умолчанию 7 дней — только для background-сессий). |
httpMaximumConnectionsPerHost | Int | Макс. одновременных соединений к одному хосту (по умолчанию 6). |
httpCookieAcceptPolicy | HTTPCookie.AcceptPolicy | .always, .never, .onlyFromMainDocumentDomain |
urlCache | URLCache? | Кэш (по умолчанию — общий для default-сессии). |
requestCachePolicy | NSURLRequest.CachePolicy | .useProtocolCachePolicy, .reloadIgnoringLocalCacheData, .returnCacheDataElseLoad, .returnCacheDataDontLoad |
networkServiceType | URLSessionTask.NetworkServiceType | .default, .voip, .video, .background, .voice, .networkServiceTypeResponsiveData |
sharedContainerIdentifier | String? | Для background-сессий: указывает app group для обмена данными между приложением и extension’ами. |
sessionSendsLaunchEvents | Bool | Для background-сессий: отправлять ли application(_:handleEventsForBackgroundURLSession:completionHandler:) при launch. |
5.1.3. URLSessionTask — ключевые свойства
| Свойство | Тип | Описание |
|---|
taskIdentifier | Int | Уникальный ID задачи в рамках сессии. |
originalRequest, currentRequest | URLRequest? | original — как создана, current — с учётом редиректов. |
response | URLResponse? | Ответ от сервера (после didReceive). |
countOfBytesSent, countOfBytesReceived | Int64 | Счётчики передачи (актуальны в didSendBodyData, didCompleteWithError). |
state | URLSessionTask.State | .suspended, .running, .canceling, .completed |
priority | Float | 0.0–1.0 (по умолчанию 0.5). Влияет на порядок отправки в httpMaximumConnectionsPerHost. |
earliestBeginDate | Date? | Мин. дата начала (для BGAppRefreshTask → URLSession). |
metrics | URLSessionTaskMetrics? | Только после завершения задачи. |
5.1.4. URLSessionTaskMetrics — детализация времени
Доступна в urlSession(_:task:didCompleteWithError:) → (task as? URLSessionDataTask)?.transactionMetrics.
| Свойство | Тип | Описание |
|---|
taskInterval | TimeInterval | Полное время выполнения. |
redirectCount | Int | Количество редиректов. |
transactionMetrics | [URLSessionTaskTransactionMetrics] | По одному на каждый «хоп» (оригинал + редиректы). |
Для каждого URLSessionTaskTransactionMetrics:
| Свойство | Описание |
|---|
fetchStartDate | Начало DNS lookup. |
domainLookupStartDate, domainLookupEndDate | Время DNS. |
connectStartDate, connectEndDate | Установка TCP-соединения. |
secureConnectionStartDate, secureConnectionEndDate | TLS handshake. |
requestStartDate | Отправка HTTP-заголовков. |
requestEndDate | Завершение отправки тела (если есть). |
responseStartDate | Получение первых байт ответа. |
responseEndDate | Завершение получения тела. |
✅ Пример диагностики медленного запроса:
let dns = metric.domainLookupEndDate.timeIntervalSince(metric.domainLookupStartDate)
let tls = metric.secureConnectionEndDate.timeIntervalSince(metric.secureConnectionStartDate)
let ttfb = metric.responseStartDate.timeIntervalSince(metric.requestEndDate)
5.1.5. Background URLSession — ограничения и требования
- Делегат должен быть сильной ссылкой (обычно — singleton или retained во
AppDelegate).
- В
application(_:handleEventsForBackgroundURLSession:completionHandler:) — сохранить completionHandler, вызвать его после urlSessionDidFinishEvents(forBackgroundURLSession:).
- Не поддерживает
uploadTask(with:from:) с Data — только с FileURL.
- Не поддерживает интерактивные задачи (streaming, WebSocket).
- Макс. 100 задач на сессию (иначе —
NSURLErrorBackgroundSessionInUseByAnotherProcess).
5.2. CLLocationManager: Точность, энергопотребление, режимы
5.2.1. Свойства и их влияние
| Свойство | Тип | Возможные значения / Примечания |
|---|
desiredAccuracy | CLLocationAccuracy | |
kCLLocationAccuracyBestForNavigation — GPS + motion, высокая точность, высокое энергопотребление (только для foreground)
kCLLocationAccuracyBest — GPS, высокая точность
kCLLocationAccuracyNearestTenMeters
kCLLocationAccuracyHundredMeters — смартфонный GPS (рекомендуется для большинства случаев)
kCLLocationAccuracyKilometer, ThreeKilometers — cell/wifi only
kCLLocationAccuracyReduced (iOS 14+) — ~200 м, низкое энергопотребление |
| distanceFilter | CLLocationDistance | Мин. расстояние между update’ами (в метрах). kCLDistanceFilterNone = каждый update. |
| activityType | CLActivityType | .other, .automotiveNavigation, .fitness, .otherNavigation | Влияет на фильтрацию шума и энергопотребление. |
| pausesLocationUpdatesAutomatically | Bool | Приостанавливать updates, если пользователь неподвижен (по умолчанию true). |
| showsBackgroundLocationIndicator | Bool (iOS 15+) | Показывать индикатор в status bar при фоновой геолокации (по умолчанию true). |
| allowsBackgroundLocationUpdates | Bool | Разрешить updates в фоне (требует UIBackgroundModes: location). |
5.2.2. Режимы мониторинга
| Режим | Метод | Энергоэффективность | Точность | Использование |
|---|
| Continuous | startUpdatingLocation() | Низкая | Высокая | Навигация, фитнес-трекеры |
| Significant Change | startMonitoringSignificantLocationChanges() | Очень высокая | ~500 м | Общее местоположение (погода, город) |
| Region Monitoring | startMonitoring(for:) (CLCircularRegion, CLBeaconRegion) | Высокая | Вход/выход из радиуса | Геозоны, маяки |
| Deferred Updates | allowDeferredLocationUpdates(untilTraveled:timeout:) | Средняя | Как в continuous | Пакетная передача точек (например, при записи трека) |
| Visit Monitoring | startMonitoringVisits() | Очень высокая | Прибытие/уход из места | Поведенческий анализ |
⚠️ Ограничения:
significantLocationChanges не работает в симуляторе.
region monitoring поддерживает до 20 регионов одновременно.
deferred updates работают только при desiredAccuracy = kCLLocationAccuracyBest и activityType = .fitness.
5.2.3. Диагностика ошибок
Код ошибки (CLError.Code) | Причина | Действие |
|---|
.denied | Пользователь запретил | Проверить authorizationStatus, вести в Settings |
.network | Нет сети/GPS | Повторить позже, использовать кэш |
.headingFailure | Нет магнитометра | Отключить compass |
.rangingUnavailable | Bluetooth выключен | Проверить CBCentralManager.state |
.regionMonitoringDenied, .regionMonitoringFailure | Ограничения системы | Не более 20 регионов, не менее 25 м радиус |
5.3. Core Data: Контексты, конкурентность, производительность
5.3.1. NSPersistentContainer — стандартная конфигурация
| Свойство | Тип | Описание |
|---|
name | String | Имя модели данных (.xcdatamodeld). |
persistentStoreDescriptions | [NSPersistentStoreDescription] | Описания хранилищ (SQLite по умолчанию). |
viewContext | NSManagedObjectContext | Main-queue context для UI. Не используйте для записи в фоне. |
performBackgroundTask(_:) | func | Создаёт новый private-queue context, выполняет блок, сохраняет, сливает в viewContext. |
5.3.2. Типы NSManagedObjectContext
| Тип | Создание | Поток | Использование |
|---|
| Main Queue | NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) | Главный поток | Только чтение или UI-обновления. |
| Private Queue | NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) | Отдельная очередь | Запись, импорт, тяжёлые операции. |
| Confinement | .confinementConcurrencyType (устарело в iOS 10) | Любой, но один поток | Не рекомендуется. |
✅ Правильный паттерн для записи:
container.performBackgroundTask { backgroundContext in
let obj = NSEntityDescription.insertNewObject(forEntityName: "User", into: backgroundContext)
obj.name = "Timur"
try? backgroundContext.save()
}
5.3.3. Merge Policies
| Политика | Поведение при конфликте (внешнее изменение vs локальное) |
|---|
.error | Выбрасывает NSMergeConflict. |
.rollback | Откатывает локальные изменения. |
.overwrite | Перезаписывает внешние изменения локальными. |
.mergeByPropertyObjectTrump | Приоритет локального объекта (по свойствам). |
.mergeByPropertyStoreTrump | Приоритет данных из хранилища. |
5.3.4. Производительность: Faulting, Prefetching, Batching
| Механизм | Свойство / Метод | Эффект |
|---|
| Faulting | NSManagedObject.isFault | Ленивая загрузка атрибутов/relationship’ов. |
| Prefetching | NSFetchRequest.relationshipKeyPathsForPrefetching = ["author", "tags"] | Загрузка связанных объектов одним запросом. |
| Batch Faulting | NSFetchRequest.fetchBatchSize = 20 | Загружать объекты пакетами (уменьшает peak memory). |
| Limit | fetchRequest.fetchLimit = 100 | Ограничение выборки. |
| Asynchronous Fetch | NSAsynchronousFetchRequest | Фоновая выборка с прогрессом. |
| Batch Updates | NSBatchUpdateRequest | Обновление без загрузки объектов в память (UPDATE SQL). |
⚠️ Опасные практики:
- Доступ к
NSManagedObject вне perform(_:) → CoreData: fault: ... was not updated correctly.
- Передача
NSManagedObjectID вместо объекта между контекстами — безопасно.
5.4. Keychain Services: Доступность, шаринг, биометрия
5.4.1. kSecAttrAccessible — когда данные доступны
| Значение | Доступность |
|---|
kSecAttrAccessibleWhenUnlocked | Только после разблокировки (после reboot — недоступно). Рекомендуется по умолчанию. |
kSecAttrAccessibleAfterFirstUnlock | После первого разблокирования (даже в фоне). |
kSecAttrAccessibleAlways | Всегда (включая reboot). Удалено в iOS 9+ (заменено на WhenUnlocked). |
...ThisDeviceOnly суффикс | Не синхронизируется в iCloud/iTunes. |
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly | Только если включён пароль + не синхронизируется. |
5.4.2. Совместный доступ (App Groups)
| Параметр | Значение |
|---|
kSecAttrAccessGroup | "group.com.example.app" — должен совпадать с entitlement. |
| Entitlement | com.apple.security.application-groups |
5.4.3. Биометрия (LocalAuthentication + Keychain)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "user_token",
kSecAttrService as String: "com.example.app",
kSecUseAuthenticationUI as String: kSecUseAuthenticationUIFail,
kSecUseAuthenticationContext as String: context
]
| Ключ | Значение | Эффект |
|---|
kSecUseAuthenticationUI | kSecUseAuthenticationUIFail, kSecUseAuthenticationUIAllow, kSecUseAuthenticationUISkip | Показывать ли system prompt. |
kSecUseOperationPrompt | "Подтвердите вход Face ID" | Текст в системном диалоге. |
kSecAttrTokenID | kSecAttrTokenIDSecureEnclave | Хранить в Secure Enclave (только для ключей, не для паролей). |
⚠️ Ограничения:
- Данные с
kSecAttrAccessibleWhenPasscodeSet... недоступны, если пароль не установлен.
- Биометрия работает только на устройствах с Touch ID/Face ID. Проверяйте
LAContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error:).
5.5. App Extensions: Типы, Context, Data Sharing
5.5.1. Поддерживаемые типы расширений (iOS)
| Тип | NSExtensionPointIdentifier | Описание |
|---|
| Today Widget | com.apple.widgetkit-extension | WidgetKit (iOS 14+). Устаревший com.apple.widget-extension (pre-iOS 14). |
| Share | com.apple.share-services | Расшаривание контента. |
| Action | com.apple.services | Обработка выделенного текста/изображения. |
| Photo Editing | com.apple.photo-editing | Редактор в Photos.app. |
| Document Provider | com.apple.fileprovider-nonui / -ui | Интеграция с Files.app. |
| Custom Keyboard | com.apple.keyboard-service | Кастомная клавиатура. |
| Intent | com.apple.intents-service | Siri Shortcuts, Spotlight suggestions. |
| Intent UI | com.apple.intents-ui-service | Кастомный интерфейс для intent’а. |
| Notification Content | com.apple.usernotifications.content-extension | Кастомный UI уведомления. |
| Notification Service | com.apple.usernotifications.service | Изменение уведомления перед показом (media, encryption). |
5.5.2. NSExtensionContext — ключевые свойства
| Свойство | Тип | Описание |
|---|
inputItems | [NSExtensionItem] | Входные данные (текст, URL, изображения). |
attachments | [NSItemProvider] | Файлы/данные через NSItemProvider. |
hostProtocol | NSXPCConnection | Для кастомного IPC с хост-приложением (редко). |
completeRequest(returningItems:completionHandler:) | func | Завершить расширение и вернуть результат. |
cancelRequest(withError:) | func | Отменить с ошибкой. |
5.5.3. Обмен данными с основным приложением
| Механизм | Ограничения |
|---|
App Groups (UserDefaults(suiteName:), FileManager.containerURL(forSecurityApplicationGroupIdentifier:)) | Общий UserDefaults, файлы, Core Data (через shared container). |
| Keychain Sharing | Через kSecAttrAccessGroup. |
| URL Schemes | open(_:options:completionHandler:) → application(_:open:options:). |
Handoff (NSUserActivity) | Только для public.app-category данных. |
| CloudKit / iCloud Drive | Требует включения iCloud entitlement’ов. |
⚠️ Важно:
- Расширение не имеет доступа к mainBundle — только к своему bundle.
- Время выполнения ограничено (~30 сек для большинства, ~20 мс для keyboard).
- Не используйте
UIApplication.shared — он недоступен в extension’ах.
Фокус — на публичных, стабильных API, runtime-диагностике, совместимости, а также на ограничениях и edge cases, критичных для production-развёртывания.
6.1. SwiftUI: Environment, Modifiers, Lifecycle, UIKit Interop
6.1.1. Ключевые @Environment значения (доступны через @Environment(\.key))
| Ключ | Тип | Описание | Примечания |
|---|
\.colorScheme | ColorScheme | .light, .dark | Соответствует UITraitCollection.userInterfaceStyle. |
\.sizeClass.horizontal, \.sizeClass.vertical | UserInterfaceSizeClass? | .compact, .regular, nil | Для адаптации под split view/iPad. |
\.dynamicTypeSize | DynamicTypeSize | .xxxSmall, .accessibilityXXXLarge | Замена UIContentSizeCategory. |
\.legibilityWeight | LegibilityWeight | .regular, .bold | Для lock screen/нотификаций. |
\.layoutDirection | LayoutDirection | .leftToRight, .rightToLeft | Управляется через UIView/userInterfaceLayoutDirection. |
\.locale | Locale | Текущая локаль (из UITraitCollection.preferredLanguages). | |
\.redactionReasons | RedactionReasons | .placeholder (при redacted(reason:)) | Для skeleton state’ов. |
\.isEnabled | Bool | Состояние disabled (наследуется от родителя). | |
\.editMode | Binding<EditMode>? | Режим редактирования (например, EditButton()). | |
\.horizontalSizeClass, \.verticalSizeClass | UserInterfaceSizeClass? | То же, что и sizeClass, но deprecated в пользу \.sizeClass. | |
\.scenePhase | ScenePhase | .active, .inactive, .background | Аналог UIApplication.State. |
\.dismiss | DismissAction | @Environment(\.dismiss) var dismiss → dismiss() | Для программного закрытия sheet/navigationStack. |
\.openURL | OpenURLAction | @Environment(\.openURL) var openURL → openURL(URL(string:"...")!) | Безопасная замена UIApplication.shared.open. |
\.colorSchemeContrast | ColorSchemeContrast | .standard, .increased (iOS 17+) | Для Accessibility → Increase Contrast. |
✅ Пример адаптации под тему:
struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
Text("Текст")
.foregroundColor(colorScheme == .dark ? .white : .black)
}
}
6.1.2. Жизненный цикл View
| Modifier | Когда вызывается | Аналог в UIKit |
|---|
.onAppear { } | После вставки в иерархию и первого render’а | viewDidAppear(_:) |
.onDisappear { } | Перед удалением из иерархии | viewDidDisappear(_:) |
.task { } | При появлении + отмена при исчезновении (через Task.cancel()). | viewDidAppear + viewDidDisappear с Task |
.task(id: value) { } | Перезапуск при изменении id | — |
.onChange(of: value) { } | При изменении значения (только если view в иерархии) | didSet + проверка isViewLoaded |
.onReceive(Publisher) { } | При получении события от publisher’а | NotificationCenter.addObserver |
⚠️ Важно:
init() у View вызывается много раз — не используйте для side effects.
- Для долгих операций — только
.task или .onReceive с @StateObject.
6.1.3. Согласование SwiftUI и UIKit
| Сценарий | Решение |
|---|
| Встраивание UIKit в SwiftUI | UIViewRepresentable / UIViewControllerRepresentable |
| Доступ к UIKit-состоянию из SwiftUI | Coordinator в Representable + @Binding |
| Передача данных в UIKit | updateUIView(_:, context:) вызывается при изменении @State, @Binding, @Environment. |
| SwiftUI → UIKit navigation | Использовать UIHostingController как root/modal VC. |
| UIKit → SwiftUI navigation | В UIHostingController — rootView: some View. |
Общий Environment | Через UIHostingController.rootView = YourView().environment(\.key, value). |
✅ Пример UIViewRepresentable с gesture:
struct CustomView: UIViewRepresentable {
@Binding var value: CGFloat
func makeUIView(context: Context) -> UIView {
let view = UIView()
let pan = UIPanGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.panned))
view.addGestureRecognizer(pan)
context.coordinator.view = view
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
}
class Coordinator {
var view: UIView?
@Binding var value: CGFloat
init(_ value: Binding<CGFloat>) { self._value = value }
@objc func paned(_ g: UIPanGestureRecognizer) { }
}
func makeCoordinator() -> Coordinator { Coordinator($value) }
}
6.2. Accessibility в SwiftUI
| Modifier | Описание | Пример |
|---|
.accessibilityLabel("Отправить") | Замена текста для VoiceOver. | Button("Send").accessibilityLabel("Отправить") |
.accessibilityHint("Дважды нажмите для отправки") | Подсказка. | |
.accessibilityValue("50%") | Текущее значение (sliders, progress). | |
.accessibilityAddTraits(.isButton) | Добавить trait. | .accessibilityAddTraits(.isHeader) |
.accessibilityRemoveTraits(.isImage) | Удалить trait. | |
.accessibilityHidden(true) | Скрыть от VoiceOver. | Для decorative views. |
.accessibilitySortPriority(1) | Порядок обхода (выше — раньше). | |
.accessibilityRotor("Элементы", entries: items) | Кастомный rotor (iOS 16+). | Позволяет быстро переключаться между группами. |
.accessibilityFocused($isFocused) | Отслеживать фокус (например, для scroll-to). | ScrollView { TextField(...).accessibilityFocused($focused) } |
.accessibilityRepresentation { ... } | Замена view на accessibility-дереве (iOS 17+). | Для сложных кастомных элементов. |
✅ Диагностика:
- В симуляторе:
Settings → Accessibility → VoiceOver + Accessibility Inspector.
print(AccessibilityAudit(view)) — статический анализ (требуется import SwiftUIAccessibility).
6.3.1. Поддерживаемые семейства (iOS)
| Семейство | Размер (pt) | Описание |
|---|
.systemSmall | 110×110 | Одна иконка/текст. |
.systemMedium | 220×110 | Компактный контент. |
.systemLarge | 220×220 | Богатый контент (stack, grid). |
.accessoryRectangular | 110×30 | В строке уведомлений/Lock Screen. |
.accessoryInline | ~110×20 | Внутри уведомления. |
.accessoryCircular | 68×68 | Круглая иконка (Lock Screen). |
⚠️ Ограничения:
- Нет интерактивности (только deep link через
URLScheme).
- Нет network requests в
getTimeline(...). Только кэш/фоновая загрузка.
- Макс. 15 виджетов на устройство от одного приложения.
6.3.2. TimelineProvider
| Метод | Описание |
|---|
placeholder(in:) | Skeleton-состояние (показывается при первом добавлении). |
getSnapshot(...) → Entry | Preview в редакторе виджетов. |
getTimeline(...) → Timeline<Entry> | Реальные данные. Timeline(entries: [...], policy: .atEnd) |
TimelineReloadPolicy | Поведение |
|---|
.atEnd | Обновить после последнего entry. |
.after(Date) | Обновить после указанного времени. |
.never | Только вручную (через WidgetCenter.shared.reloadTimelines). |
6.3.3. Deep Linking из виджета
Link(destination: URL(string: "myapp://dashboard?widget=1")!) {
Text("Открыть")
}
✅ Требует регистрации схемы в Info.plist (CFBundleURLTypes).
✅ В AppDelegate/SceneDelegate — обработка через open(_:options:).
6.4. Push Notifications: APNs, Payload, Critical Alerts
6.4.1. Обязательные поля payload (JSON)
{
"aps": {
"alert": {
"title": "Заголовок",
"body": "Текст",
"subtitle": "Подзаголовок"
},
"badge": 1,
"sound": "default",
"thread-id": "group1",
"category": "MESSAGE_CATEGORY"
},
"custom_key": "value"
}
| Поле | Тип | Описание |
|---|
alert | string или dict | Если string — только body. |
badge | Int | Число на иконке. 0 — скрыть. |
sound | string | "default" или имя .caf файла в bundle. |
content-available | 1 | Silent push (фоновое обновление). |
mutable-content | 1 | Разрешить Notification Service Extension модифицировать. |
category | string | Для action buttons (регистрируется через UNNotificationCategory). |
6.4.2. Critical Alerts (требуют entitlement com.apple.developer.usernotifications.critical-alert)
| Дополнительные поля | Описание |
|---|
"sound": { "critical": 1, "name": "alert.caf", "volume": 1.0 } | Принудительное воспроизведение даже при silent mode. |
"interruption-level": "critical" | iOS 15+: вместо critical:1 в sound. |
⚠️ Critical alerts требуют одобрения Apple в App Review с justification.
6.4.3. Communication Notifications (iOS 15+, com.apple.developer.usernotifications.communication)
| Поле | Значение |
|---|
"interruption-level": "time-sensitive" | Высокий приоритет (но не critical). Показывается поверх экрана блокировки. |
"relevance-score": 0.8 | Релевантность (0.0–1.0) для сортировки. |
✅ Используется для звонков, сообщений, экстренных уведомлений.
6.5. App Clips: Discovery, Invocation, Handoff
6.5.1. Способы запуска
| Триггер | Требования |
|---|
| NFC tag | #appclip в NDEF record. |
| QR code | URL с ?clip_id=... + AASA-файл. |
| Safari Smart Banner | <meta name="apple-itunes-app" content="app-clip-id=..."> |
| Messages link preview | URL с поддержкой App Clip. |
| Map pin | В Apple Maps → бизнес с App Clip. |
6.5.2. Ограничения
| Параметр | Значение |
|---|
| Макс. размер | 15 МБ (скачивается за < 2 сек). |
| Время жизни | До 30 дней (если не установлено полное приложение). |
| Данные | Общие с основным приложением через App Groups/Keychain. |
| Handoff | NSUserActivity с supportsAppClip → application(_:continue:restorationHandler:). |
6.5.3. Диагностика
- В Xcode:
Window → Devices and Simulators → App Clips — просмотр установленных.
AppClip.isRunningInAppClip() — runtime-проверка.
AppClip.requestEphemeralUserNotification(...) — показ временного уведомления (до 4 часов).
📘 Security, Testing, Localization, Distribution, Debugging
Фокус — на инструментах и атрибутах, критичных для production-стабильности, прохождения App Review, локализации и отладки на уровне инженерной диагностики.
7.1. Security: ATS, Certificate Pinning, SSL, Biometry, Secure Enclave
7.1.1. App Transport Security (ATS)
Конфигурация в Info.plist → NSAppTransportSecurity.
| Ключ | Тип | Описание | Рекомендация |
|---|
NSAllowsArbitraryLoads | Bool | Отключает ATS глобально. | ❌ Не использовать — отклонят в App Review без justification. |
NSExceptionDomains | dict | Исключения для доменов. | ✅ Использовать только для legacy API. |
— example.com | dict | — | — |
— — NSExceptionAllowsInsecureHTTPLoads | Bool | Разрешить HTTP. | Только если нет HTTPS. |
— — NSIncludesSubdomains | Bool | Применить к поддоменам. | |
— — NSExceptionMinimumTLSVersion | String | TLSv1.1, TLSv1.2, TLSv1.3 | Минимум — TLSv1.2. |
— — NSExceptionRequiresForwardSecrecy | Bool | Требовать PFS (ECDHE). | По умолчанию true. |
— — NSRequiresCertificateTransparency | Bool | Требовать CT logs. | Рекомендуется для финансовых сервисов. |
✅ Пример безопасного исключения:
<key>NSExceptionDomains</key>
<dict>
<key>legacy-api.example.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<false/>
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.2</string>
<key>NSExceptionRequiresForwardSecrecy</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
7.1.2. Certificate Pinning
Реализуется через URLSessionDelegate → urlSession(_:task:didReceive:completionHandler:).
| Подход | Описание | Риски |
|---|
| Public Key Pinning | Сравнение SHA-256 от SecKeyCopyExternalRepresentation. | Устойчив к смене сертификата при том же ключе. |
| Certificate Pinning | Сравнение SHA-256 от DER-представления сертификата. | Требует обновления при ротации сертификата. |
| Backup Pins | Хранить ≥2 пина (основной + запасной). | Избегает downtime при ротации. |
⚠️ Важно:
- Не пинить корневой CA — только leaf/middle.
- Обеспечить механизм отката (например, через remote config).
- Использовать
SecTrustEvaluateWithError (iOS 12+) вместо SecTrustEvaluate.
7.1.3. LocalAuthentication: Биометрия и аутентификация
| Policy | Описание |
|---|
.deviceOwnerAuthentication | Face ID / Touch ID или пароль. |
.deviceOwnerAuthenticationWithBiometrics | Только биометрия. |
.deviceOwnerAuthenticationWithWatch | Face ID + Apple Watch (iOS 16.4+). |
Ключи контекста (LAContext) | Тип | Описание |
|---|
localizedReason | String | Обязательно: «Для входа в приложение». |
localizedCancelTitle | String | Кнопка отмены. |
touchIDAuthenticationAllowableReuseDuration | TimeInterval | Переиспользование Touch ID (устарело). |
interactionNotAllowed | Bool | Не показывать UI (только проверить). |
✅ Диагностика:
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
context.evaluatePolicy(.deviceOwnerAuthentication) { success, error in
}
} else {
}
7.1.4. Secure Enclave и Keychain
| Атрибут | Значение | Эффект |
|---|
kSecAttrTokenID | kSecAttrTokenIDSecureEnclave | Ключ генерируется и хранится только в Secure Enclave. |
kSecAttrAccessible | kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly | Доступ только при установленном пароле. |
kSecUseAuthenticationUI | kSecUseAuthenticationUIFail | Проверка без UI. |
SecAccessControlCreateFlags.biometryCurrentSet | — | Ключ недействителен после сброса биометрии. |
✅ Генерация ключа в Secure Enclave:
let attributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits as String: 256,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: "com.example.key"
]
]
let key = SecKeyCreateRandomKey(attributes as CFDictionary, &error)
7.2.1. XCTestCase: Key Properties & Methods
| Свойство/Метод | Описание |
|---|
continueAfterFailure = false | Остановка теста при первом XCTAssert fail. |
measure { } | Измерение времени выполнения (среднее из 10 итераций). |
XCTExpectation + waitForExpectations(timeout:handler:) | Асинхронные тесты. |
XCTActivity | Группировка шагов в отчёте. |
addUIInterruptionMonitor(withDescription:handler:) | Обработка системных alert’ов (например, permission). |
7.2.2. UI Testing: XCUIElement Queries
| Query | Описание |
|---|
app.buttons["submit"] | По accessibility identifier. |
app.textFields.matching(identifier: "email").firstMatch | Фильтрация. |
app.descendants(matching: .textField) | Все текстовые поля. |
element.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() | Тап в центр элемента. |
⚠️ Best practices:
- Использовать
accessibilityIdentifier, а не label.
- Не полагаться на текст/позицию — только на идентификаторы.
sleep() → заменить на XCTWaiter.wait(for:timeout:).
| Инструмент | Использование |
|---|
| Time Profiler | CPU-bound bottlenecks (viewDidLoad, cellForRowAt). |
| Allocations | Утечки памяти, retain cycles. |
| Leaks | Автоматическое обнаружение утечек. |
| Core Data | «Core Data Fetches», «Core Data Saves». |
| Energy Log | Рейтинг энергоэффективности (App Store требует ≥3/5). |
✅ Запуск из кода:
measure(metrics: [XCTClockMetric(), XCTCPUMetric(), XCTMemoryMetric()]) {
heavyOperation()
}
7.2.4. XCTestPlan (Xcode 11+)
| Возможность | Описание |
|---|
| Multiple configurations | Разные DEBUG/RELEASE, языки, devices. |
| Test selection | Включение/исключение тестов по тегам (#XCTTag("smoke")). |
| Environment variables | XCInjectBundle, XCTestConfiguration. |
| Parallel testing | Запуск на нескольких симуляторах одновременно. |
7.3. Localization: .xcstrings, Pluralization, Pseudolocalization
7.3.1. Формат .xcstrings (Xcode 15+)
{
"strings": {
"welcome_message": {
"extractionState": "manual",
"localizations": {
"ru": {
"stringUnit": {
"state": "translated",
"value": "Добро пожаловать, %@"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Welcome, %@"
}
}
}
}
}
}
Преимущество перед .strings | Описание |
|---|
| Валидация форматов | Xcode проверяет %@, %d на этапе сборки. |
| Поддержка plural/gender | Встроенно (см. ниже). |
| Integration с Xcode Cloud | Автоматическая синхронизация. |
7.3.2. Pluralization (.stringsdict или .xcstrings)
<dict>
<key>files_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@files@</string>
<key>files</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>ld</string>
<key>zero</key>
<string>Нет файлов</string>
<key>one</key>
<string>%ld файл</string>
<key>few</key>
<string>%ld файла</string>
<key>many</key>
<string>%ld файлов</string>
<key>other</key>
<string>%ld файлов</string>
</dict>
</dict>
</dict>
✅ Использование:
String(format: NSLocalizedString("files_count", comment: ""), filesCount)
7.3.3. Pseudolocalization (для тестирования layout)
В Info.plist → CFBundleDevelopmentRegion = en, и добавить язык:
Xcode автоматически:
- Удлиняет строки на 40%
- Добавляет
[!! и !!] по краям
- Заменяет латиницу на акцентированные символы («Ţḗşţ»)
✅ Включается в scheme → Options → Application Language → Pseudolanguage.
7.4. Distribution: App Store Connect API, Notarization, Thinning
7.4.1. App Store Connect API
| Эндпоинт | Использование |
|---|
POST /v1/apps | Создание нового приложения. |
GET /v1/builds?filter[app]=... | Список сборок. |
PATCH /v1/builds/{id} | Изменение статуса («Ready for Sale»). |
POST /v1/betaGroups/{id}/builds | Добавление сборки в TestFlight. |
✅ Требуется JWT-токен с private key (.p8) и scopes:
app_management, build_upload, beta_tester_invites.
7.4.2. Notarization (для macOS, но затрагивает iOS при Universal)
| Шаг | Команда |
|---|
| Архивация | xcodebuild -archivePath ... archive |
| Экспорт | xcodebuild -exportArchive -exportOptionsPlist export.plist |
| Отправка | xcrun notarytool submit ... --wait |
| Stapling | xcrun stapler staple MyApp.app |
⚠️ Для iOS notarization не требуется, только для macOS/iPadOS (Catalyst).
7.4.3. App Thinning & Slicing
| Механизм | Описание |
|---|
| Bitcode | Промежуточный код для re-optimization Apple’ом. Устарел в Xcode 14+ (но поддерживается). |
| On-Demand Resources | Загрузка asset’ов по тегам (NSBundleResourceRequest). |
| Asset Catalog Slicing | @2x, @3x, universal → только нужные ресурсы в IPA. |
| Architecture Slicing | Только arm64 (удалён armv7, i386, x86_64). |
✅ Проверка содержимого IPA:
unzip App.ipa
lipo -info Payload/*.app/*.app/Executable
7.5. Debugging: LLDB, View Debugger, Memory Graph
7.5.1. Полезные LLDB команды
| Команда | Описание |
|---|
po view | Print object (вызывает debugDescription). |
v view.frame | Печать свойства без вызова методов. |
expr view.backgroundColor = UIColor.red | Изменение свойства в runtime. |
fr v | Переменные текущего фрейма. |
bt | Backtrace. |
image lookup -rn "ClassName" | Поиск символов по regex. |
memory read -c 16 -f x view.layer | Чтение памяти (hex). |
7.5.2. View Debugger (Xcode)
| Фича | Горячая клавиша |
|---|
| Debug View Hierarchy | ⌃+⇧+⌥+C |
| Показать constraints | Галочка внизу. |
| Поиск по accessibility label | В поиске — accessibilityLabel:"Submit". |
7.5.3. Memory Graph Debugger
| Анализ | Использование |
|---|
| Leaked Blocks | Утечки с retain count > 0 и нет сильных ссылок из root. |
| Cycle Detection | Граф с циклами → клик по узлу → «Cycle». |
| Filter by Type | MyViewController, __NSCFLocalDataTask. |
✅ Совет:
- Запускать на реальном устройстве — симулятор маскирует некоторые утечки.
- Использовать
DeinitLogger:
deinit { print("Deinit \(Self.self)") }
📘 Производительность, Crash Reporting, CI/CD, Документация, Deprecation
Фокус — на диагностике, мониторинге, автоматизации и поддержке кодовой базы в долгосрочной перспективе, с учётом требований к качеству, воспроизводимости и сопровождаемости.
8.1. Производительность: FPS, Rendering, Память, Энергия
8.1.1. Измерение FPS и кадровых провалов
| Метод | Описание | Точность |
|---|
| Xcode Debug Gauge | Debug → Debug Gauges → Core Animation | Средний FPS (округлён). |
| CADisplayLink | CADisplayLink(target:selector:) → timestamp, targetTimestamp | Точный FPS, per-frame latency. |
| Instruments → Core Animation | «Frame Rate», «Screen Recording» | Профилирование в условиях, близких к реальным. |
| MTLCommandBuffer completionHandler | Для Metal-рендеринга | Низкоуровневый контроль. |
✅ Пример CADisplayLink-мониторинга:
let link = CADisplayLink(target: self, selector: #selector(frame))
link.add(to: .main, forMode: .common)
@objc func frame(_ link: CADisplayLink) {
let delta = link.targetTimestamp - link.timestamp
let fps = 1.0 / (link.duration + delta)
if fps < 55 { print("⚠️ FPS: \(fps)") }
}
8.1.2. Offscreen Rendering & Overdraw
| Признак | Диагностика | Решение |
|---|
| Offscreen rendering | Instruments → Core Animation → «Color Offscreen-Rendered Yellow» | Избегать masksToBounds + shadow, groupOpacity, UIVisualEffectView в скролле. |
| Overdraw | «Color Blended Layers — Red» | Уменьшить прозрачные слои, использовать .opaque флаг. |
| Misaligned images | «Color Misaligned Images — Yellow/Green» | Выравнивать по пикселям (floor(x)), использовать @2x/@3x. |
| Слишком большие текстуры | «Color Copied Images — Blue» | Сжимать изображения до MAX_TEXTURE_SIZE (обычно 4096×4096). |
⚠️ Важно:
shouldRasterize = true → кэширует offscreen-рендеринг, но дорого при анимации.
draw(_:) в основном потоке → блокирует UI. Выносить в drawsAsynchronously = true (CALayer).
8.1.3. Texture Memory & GPU
| Инструмент | Показатель | Норма |
|---|
| Instruments → Metal System Trace | Texture Memory, Buffer Memory | < 100 МБ на iPhone Pro, < 50 МБ на старых моделях. |
| Xcode Memory Report | «GPU Memory» | Не должен превышать 50% от IOSurface limit. |
| JetStream 2 (браузерный JS-бенчмарк) | — | Не для iOS, но полезен для WebView-компонентов. |
✅ Оптимизации:
- Использовать
UIImage.SymbolConfiguration.scale(.large) вместо растровых иконок.
CATiledLayer для больших изображений (карты, PDF).
UIGraphicsImageRendererFormat.preferredRange = .standard (не .extended) для экономии памяти.
8.1.4. Energy Impact
| Показатель | Диагностика | Причины высокого потребления |
|---|
| CPU Wakeups | Energy Log → «CPU wakeups / sec» | Таймеры (Timer), polling, CADisplayLink без паузы. |
| Location Accuracy | kCLLocationAccuracyBest в фоне | Использовать significantLocationChanges или reduced. |
| Network Calls | Battery Usage → «Network» | Частые мелкие запросы → объединять в batch. |
| Background Activity | «Background energy use» | beginBackgroundTask без endBackgroundTask. |
✅ Рекомендации Apple:
- Минимизировать
DispatchSourceTimer с малым leeway.
- Использовать
URLSessionConfiguration.timeoutIntervalForResource для долгих задач.
- Отключать
CLLocationManager при applicationDidEnterBackground.
8.2. Crash Reporting: Symbolication, Форматы, Интеграции
8.2.1. Форматы крашей
| Тип | Файл | Описание |
|---|
| Crash Report | .crash | Содержит threads, registers, binary images. |
| Diagnostic Report | .ips | Аналогично, но с дополнительными метаданными (iOS ≥12). |
| dSYM | .dSYM | Отладочные символы для symbolication. |
🔍 Структура краша:
Thread 0 Crashed:
0 MyApp 0x0000000100005a34 0x100000000 + 23156
1 UIKitCore 0x0000000184e12abc -[UIApplication sendAction:to:forEvent:] + 96
...
Binary Images:
0x100000000 - 0x10000ffff MyApp arm64 <UUID> /var/containers/Bundle/Application/.../MyApp.app/MyApp
8.2.2. Symbolication
| Метод | Команда |
|---|
| atos | atos -arch arm64 -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp -l 0x100000000 0x0000000100005a34 |
| symbolicatecrash | symbolicatecrash report.crash MyApp.app.dSYM |
| Xcode Organizer | Автоматическая symbolication при upload в App Store Connect. |
⚠️ Требования:
- UUID бинарника в краше должен совпадать с UUID в dSYM (
dwarfdump --uuid MyApp).
- dSYM должен быть загружен в App Store Connect / Crashlytics / Sentry.
8.2.3. Интеграции
| Сервис | Особенности |
|---|
| Firebase Crashlytics | Бесплатно, интеграция через CocoaPods/SPM, NDK-поддержка. |
| Sentry | Поддержка NDK, символы через Sentry CLI, кастомные tags/breadcrumbs. |
| PLCrashReporter | Локальный сбор крашей (open-source, используется в Crashlytics). |
| KSCrash | Advanced features: signal handling, C++ exceptions, Mach exceptions. |
✅ Рекомендуемый workflow:
- Генерация dSYM при сборке (
DEBUG_INFORMATION_FORMAT = dwarf-with-dsym).
- Upload dSYM в Crashlytics/Sentry (автоматически через
upload-symbols скрипт).
- Symbolication в облаке → отчёты с именами методов и строками кода.
8.3. CI/CD: Xcode Cloud, Fastlane, xcodebuild
8.3.1. Xcode Cloud (Apple)
| Возможность | Ограничения |
|---|
| Тестирование на реальных устройствах | Только для enrolled devices в Developer Account. |
| Триггеры: push, PR, ручной запуск | Макс. 500 минут/месяц (free tier). |
| Интеграция с TestFlight | Автоматическая загрузка сборок. |
ci_post_xcodebuild.sh | Кастомные скрипты после сборки. |
✅ Конфигурация: .xccurrentversion + ci_scripts/ в репозитории.
8.3.2. Fastlane
| Команда | Назначение |
|---|
match | Синхронизация сертификатов и профилей через репозиторий. |
gym | Сборка IPA (xcodebuild archive + export). |
pilot | Загрузка в TestFlight. |
deliver | Управление метаданными App Store. |
snapshot | Автоматические скриншоты для App Store. |
✅ Пример Fastfile:
lane :beta do
match(type: "appstore")
gym(scheme: "MyApp", export_method: "app-store")
pilot
end
8.3.3. xcodebuild CLI — ключевые флаги
| Флаг | Описание |
|---|
-scheme MyApp | Схема для сборки. |
-destination 'generic/platform=iOS' | Целевая платформа. |
-archivePath build/MyApp.xcarchive | Путь архива. |
-exportOptionsPlist export.plist | Конфигурация экспорта (ad-hoc, app-store, development). |
-allowProvisioningUpdates | Автоматическое обновление provisioning profiles. |
-clonedSourcePackagesDirPath build/spm | Кэш SPM-зависимостей. |
✅ Генерация export.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store</string>
<key>uploadBitcode</key>
<false/>
<key>compileBitcode</key>
<false/>
<key>provisioningProfiles</key>
<dict>
<key>com.example.MyApp</key>
<string>iOS_Distribution_Profile</string>
</dict>
</dict>
</plist>